package conf

import (
	"strings"
	"sync"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/zeast/logs"

	jc "epg/jmconf"
	"epg/jmconf/consul"
	"epg/utils"
)

//ProxyConf proxy config.
type ProxyConf struct {
	LogSQL    bool   //record the sql to file.
	SlowQuery int64  //how many ms will record the slow query to file.
	Backlog   int64  //the max client connection per user backlog.
	AuthDSN   string //get the auth info from the instance.

	WriteTimeout int
	ReadTimeout  int
}

//NodeConf the server config
type NodeConf struct {
	Alias       string
	Addr        string
	Dbs         []string
	User        string
	Passwd      string
	Weight      int
	MaxLifeTime int32
	MaxIdleConn int32
	MaxConnNum  int32

	// Loc    *time.Location //location for parse time;
	// Strict bool
}

//CB the config callback function
type CB func(NodeConf)

//Config all the config from config center.
type Config struct {
	stop    bool
	stopCh  chan struct{}
	isReady bool
	ready   chan struct{}
	sync.RWMutex
	prefix  string
	w       jc.Watcher
	cb      map[string]map[jc.OpType]CB // [key|node] : jc.OpType : CB
	confOp  chan jc.Opt                 //for conf
	limitOp chan jc.Opt                 //for limit
	nodeOps chan []jc.Opt               //for nodes path
	Nodes   []NodeConf                  //server of mysql instances
	Proxy   ProxyConf
}

//GlobalConfig the Global Config
var (
	GlobalConfig *Config
)

//NewConfig new config instances
func NewConfig(cfg *jc.DisCfg) *Config {

	GlobalConfig = &Config{
		stopCh:  make(chan struct{}),
		ready:   make(chan struct{}),
		prefix:  cfg.Path,
		cb:      make(map[string]map[jc.OpType]CB),
		confOp:  make(chan jc.Opt, 16),
		nodeOps: make(chan []jc.Opt, 16),
		limitOp: make(chan jc.Opt, 16),
		Nodes:   make([]NodeConf, 0, 16),
	}

	watch := consul.NewCWatch(cfg)

	GlobalConfig.w = watch
	return GlobalConfig
}

//Close close the config watch and connection.
func (c *Config) Close() (err error) {
	if c.stop {
		return
	}
	c.stop = true
	close(c.stopCh)
	return
}

//GetNodes return all the Nodes now active.
func (c *Config) GetNodes() map[string]NodeConf {
	c.RLock()
	defer c.RUnlock()
	nodes := make(map[string]NodeConf)
	for _, v := range c.Nodes {
		nodes[v.Alias] = v
	}
	return nodes
}

//RegistKeyHandle regist callback on OpType
func (c *Config) RegistKeyHandle(op jc.OpType, cb CB) {
	if c.cb["key"] == nil {
		c.cb["key"] = make(map[jc.OpType]CB)
	}
	c.cb["key"][op] = cb
}

//RegistNodesHandle regist callback on OpType
func (c *Config) RegistNodesHandle(op jc.OpType, cb CB) {
	if c.cb["nodes"] == nil {
		c.cb["nodes"] = make(map[jc.OpType]CB)
	}
	c.cb["nodes"][op] = cb
}

/*
//WatchConf read data from the path: prefix + /Conf.
func (c *Config) watchConf() error {
	c.w.WatchKey("/conf", c.confOp)
	op := <-c.confOp
	err := toml.Unmarshal(op.Val, &c.Proxy)
	if err != nil {
		return err
	}
	return nil
}

//WatchNodes read data from path: prefix + /Nodes dir.
func (c *Config) watchNodes() error {
	c.w.WatchPrefix("/nodes", c.nodeOps)
	ops := <-c.nodeOps
	for _, v := range ops {
		//skip dir.
		if strings.HasSuffix(v.Key, "/") {
			continue
		}
		node := NodeConf{}
		err := toml.Unmarshal(v.Val, &node)
		if err != nil {
			logs.Errorf("nodes 信息解析失败.%s", err)
			return err
		}
		node.Alias = v.Key
		c.Nodes = append(c.Nodes, node)
	}
	return nil
}

func (c *Config) watchLimit() error {
	c.w.WatchKey("/limit", c.confOp)
	op := <-c.confOp

	var m map[string]map[string]int
	err := toml.Unmarshal(op.Val, &m)
	if err != nil {
		logs.Errorf("limit 信息解析失败. %s", err)
		return err
	}

	Limit.Update(m)
	return nil
}
*/

//Run run the watch.
func (c *Config) Run() error {
	c.w.WatchKey("/conf", c.confOp)
	//read the /conf data first, because c.Ops maybe happened before c.Op
	op := <-c.confOp
	err := toml.Unmarshal(op.Val, &c.Proxy)
	if err != nil {
		logs.Errorf("conf 信息解析失败. %s", err)
		return err
	}

	logs.Debug("解析 conf 成功")

	var lc limitConf
	c.w.WatchKey("/limit", c.limitOp)
	op = <-c.limitOp
	err = toml.Unmarshal(op.Val, &lc)
	if err != nil {
		logs.Errorf("limit 信息解析失败. %s", err)
		return err
	}

	logs.Debugf("解析 limit 成功. %v", lc)
	Limit.Update(lc)

	c.w.WatchPrefix("/nodes", c.nodeOps)

	go func() {
		defer utils.PrintPanicStack()
		for {
			select {
			case <-c.stopCh:
				logs.Infof("Close Config Watch %v", c.w.Close())
				return
			case op := <-c.confOp:
				proxyConf := ProxyConf{}
				err := toml.Unmarshal(op.Val, &proxyConf)
				if err != nil {
					logs.Error("conf 信息解析失败. %s", err)
					continue
				}
				c.Lock()
				c.Proxy = proxyConf
				c.Unlock()
				//callback
				cbs, ok := c.cb["key"]
				if ok {
					fn, ok := cbs[op.Op]
					if ok && fn != nil {
						fn(NodeConf{})
					}
				}

			case op := <-c.limitOp:
				var lc limitConf
				logs.Debugf("收到 limit 的改变. %s", string(op.Val))
				err := toml.Unmarshal(op.Val, &lc)
				if err != nil {
					logs.Errorf("limit 信息解析失败. %s", err)
					continue
				}
				logs.Debugf("解析 limit 成功. %v", lc)
				Limit.Update(lc)

			case ops := <-c.nodeOps:
				for _, v := range ops {
					//skip dir.
					if strings.HasSuffix(v.Key, "/") {
						continue
					}
					node := NodeConf{}
					err := toml.Unmarshal(v.Val, &node)
					if err != nil {
						logs.Errorf("nodes 信息解析失败.%s ", err)
						continue
					}
					node.Alias = v.Key
					// log.Infof("Notify Node Change: %v\n ", v)

					c.Lock()
					switch v.Op {
					case jc.OPAdd:
						c.Nodes = append(c.Nodes, node)
					case jc.OPDelete:
						idx := -1
						for i, n := range c.Nodes {
							if n.Alias == node.Alias {
								idx = i
							}
						}
						if idx >= 0 {
							c.Nodes = append(c.Nodes[0:idx], c.Nodes[idx+1:]...)
						}
					case jc.OPModify:
						for i, n := range c.Nodes {
							if n.Alias == node.Alias {
								c.Nodes[i] = node
							}
						}
					}
					c.Unlock()

					//callback
					cbs, ok := c.cb["nodes"]
					if ok {
						fn, ok := cbs[v.Op]
						if ok && fn != nil {
							fn(node)
						}
					}

				}
			}
			if !c.isReady && c.Proxy.Backlog > 0 && len(c.Nodes) > 0 {
				close(c.ready)
				c.isReady = true
			}
		}
	}()

WAIT:
	for {
		select {
		case <-c.ready:
			break WAIT
		case <-time.After(time.Second * 3):
			logs.Fatal("consul 配置载入超时")
		}
	}
	return nil
}
